﻿using System;
using SoftwareConsulting.BI11.NumericalMethods;
using SoftwareConsulting.BI11.NumericalMethods.MatrixAlgebra;
using System.Collections.Generic;
using SoftwareConsulting.BI11.Studio.InternalServices;

namespace SoftwareConsulting.BI11.NumericalMethods.Classification
{        
    /// <summary>
    /// Класс матрицы классификации
    /// </summary>
    public class ClassificationMatrix
    {        
        private Matrix _result;//результирующая матрицы
        private double _error;//ошибка классификации
        private bool _solved;//флаг решенной задачи
        private int[] _rangedClasses;//классы, отсортированные по убыванию точности их классификации моделью
        private int[] _classFails;//число "непопаданий" для каждого из классов (в соответсвие с порядком в _rangedClasses)
        
        /// <summary>
        /// Конструктор класса
        /// </summary>
        public ClassificationMatrix()
        {
            _solved = false;
        }

        /// <summary>
        /// Инициализирует матрицу
        /// </summary>
        /// <param name="factExamples">массив фактически классифицированных экземпляров (если Constants.NullInteger, то информации нет)</param>
        /// <param name="modelExamples">массив экземпляров, классифицированных моделью</param>
        /// <param name="classesCount">количество классов</param>
        /// <remarks>
        /// 1. Индексы обоих передаваемых массивов должны иметь одинаковую смысловую нагрузку 
        /// (0й - экземпляр 1, 1й - экземпляр 2 и т.д.)
        /// 2. Значения элементов обоих передаваемых массивов должны лежать в промежутке от 0 до classesCount-1</remarks>        
        public void BuildMatrix(int[] factExamples, int[] modelExamples, int classesCount)
        {
            _solved = false;
            _result = null;
            _error = 0;
            _rangedClasses = new int[classesCount];            
            for (int i = 0; i < classesCount; i++)
            {
                _rangedClasses[i] = i;
            }            
            if (factExamples.Length != modelExamples.Length)
                throw new BIClassificationMatrixDivergentDimensionsException(factExamples.Length, modelExamples.Length, null);
            //исключаем объекты, у которых factExamples = Constants.NullInteger
            List<int> factExamplesProcessed = new List<int>();
            List<int> modelExamplesProcessed = new List<int>();
            for (int i = 0; i < factExamples.Length; i++)
            {
                if (factExamples[i] != Constants.NullInteger)
                {
                    factExamplesProcessed.Add(factExamples[i]);
                    modelExamplesProcessed.Add(modelExamples[i]);
                }
            }
            factExamples = factExamplesProcessed.ToArray();
            modelExamples = modelExamplesProcessed.ToArray();
            //подготавливаем массивы
            double[,] components = new double[classesCount + 1, classesCount + 1]; //+1 потому что еще будут строка и столбец ИТОГО
            _classFails = new int[classesCount];
            //сначала во всей матрице нули            
            for (int i = 0; i < classesCount + 1; i++)
            {
                for (int j = 0; j < classesCount + 1; j++)
                {
                    components[i, j] = 0;            
                }
                if (i < classesCount)
                    _classFails[i] = 0;
            }
            //собственно заполнение   
            int failsSum = 0;            
            for (int i = 0; i < factExamples.Length; i++)
            {
                for (int j = 0; j < modelExamples.Length; j++)
                {
                    if (modelExamples[j] == factExamples[i])
                    {
                        int classIndex = modelExamples[j];
                        components[classIndex, classIndex] = components[classIndex, classIndex] + 1;
                        components[classIndex, classesCount] = components[classIndex, classesCount] + 1;//наращиваем ИТОГО
                        components[classesCount, classIndex] = components[classesCount, classIndex] + 1;//наращиваем ИТОГО
                    }
                    else
                    {
                        int wrongClassIndex = modelExamples[j];
                        int rightClassIndex = factExamples[i];
                        components[rightClassIndex, wrongClassIndex] = components[rightClassIndex, wrongClassIndex] + 1;
                        components[rightClassIndex, classesCount] = components[rightClassIndex, classesCount] + 1;//наращиваем ИТОГО
                        components[classesCount, wrongClassIndex] = components[classesCount, wrongClassIndex] + 1;//наращиваем ИТОГО
                        failsSum++;
                        _classFails[rightClassIndex] = _classFails[rightClassIndex] + 1;
                    }
                }
            }
            components[classesCount, classesCount] = factExamples.Length;
            //ранжируем классы по убыванию точности их классификации моделью            
            for (int i = 1; i < classesCount; i++)
            {
                int j = i;
                while (_classFails[j] < _classFails[j - 1])
                {
                    int temp = _classFails[j - 1];
                    _classFails[j - 1] = _classFails[j];
                    _classFails[j] = temp;
                    temp = _rangedClasses[j - 1];
                    _rangedClasses[j - 1] = _rangedClasses[j];
                    _rangedClasses[j] = temp;
                    j--;
                    if (j == 0)
                        break;
                }
            }
            //выдаем результат
            _result = new Matrix(components);
            _error = failsSum / factExamples.Length;
            _solved = true;
        }

        /// <summary>
        /// Возвращает решение - матрицу классификации
        /// </summary>
        /// <returns></returns>
        public Matrix GetSolutionMatrix()
        {
            if (!_solved)
                throw new BINotSolvedException(null);
            return _result;
        }

        /// <summary>
        /// Возвращает решение - ошибку классификации
        /// </summary>
        /// <returns></returns>
        public double GetSolutionError()
        {
            if (!_solved)
                throw new BINotSolvedException(null);
            return _error;
        }

        /// <summary>
        /// Возвращает решение - флаг корректности модели
        /// </summary>
        /// <param name="desiredError">максимально допустимая ошибка модели</param>
        /// <returns></returns>
        public bool GetSolutionIsCorrectModel(double desiredError)
        {
            if (!_solved)
                throw new BINotSolvedException(null);
            return _error <= desiredError;
        }

        /// <summary>
        /// Возвращает решение - классы, отсортированные по точности их классификации моделью
        /// </summary>
        /// <param name="isAscending">флаг сортировки по возрастанию ошибки</param>
        /// <returns></returns>
        public int[] GetSolutionRangedClassesByError(bool isAscending)
        {
            if (!_solved)
                throw new BINotSolvedException(null);
            if (isAscending)
                return _rangedClasses;
            int[] rangedClassesDescending = new int[_rangedClasses.Length];
            for (int i = 0; i < _rangedClasses.Length; i++)
            {
                rangedClassesDescending[_rangedClasses.Length - i - 1] = _rangedClasses[i];
            }
            return rangedClassesDescending;
        }

        /// <summary>
        /// Возвращает решение - "непопадания" классов, отсортированные по точности их классификации моделью
        /// </summary>
        /// <param name="isAscending">флаг сортировки по возрастанию ошибки</param>
        /// <returns></returns>
        public int[] GetSolutionFailsOfRangedClassesByError(bool isAscending)
        {
            if (!_solved)
                throw new BINotSolvedException(null);
            if (isAscending)
                return _classFails;
            int[] classFailsDescending = new int[_classFails.Length];
            for (int i = 0; i < _classFails.Length; i++)
            {
                classFailsDescending[_classFails.Length - i - 1] = _classFails[i];
            }
            return classFailsDescending;
        }
    }
}
